package nyist.media.utils;

import lombok.NonNull;
import org.apache.commons.lang3.time.FastDateFormat;

import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.YearMonth;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 时间工具类
 *
 * @author dcf
 * <p>
 * 已升级为java8版
 */
public class DateUtil {

    /**
     * 时间转换格式(yyyy-MM-dd)
     */
    public static final String DATE_FORMATTER_STR = "yyyy-MM-dd";

    /**
     * 时间转换格式(HH:mm)
     */
    public static final String TIME_FORMATTER_STR = "HH:mm";

    /**
     * 时间转换格式(yyyy-MM-dd HH:mm)
     */
    public static final String DATETIME_FORMATTER_STR = "yyyy-MM-dd HH:mm";

    /**
     * 时间转换格式(yyyy-MM-dd HH:mm:ss)
     */
    public static final String DATETIME_FULL_FORMATTER_STR = "yyyy-MM-dd HH:mm:ss";

    /**
     * 时间转换格式(yyyyMMdd)
     */
    public static final String DATE_FORMATTER_NO_SEPARATOR_STR = "yyyyMMdd";

    public static final ZoneId DEFAULT_ZONE_ID = ZoneId.systemDefault();

    /**
     * 日期转换缓存
     */
    private static final Map<String, FastDateFormat> DATE_FORMAT_MAP = new ConcurrentHashMap<>();

    /**
     * 时间转换器(HH:mm)
     */
    public static final DateTimeFormatter TIME_FORMATTER = DateTimeFormatter.ofPattern(TIME_FORMATTER_STR);

    /**
     * 时间日期转换器(yyyy-MM-dd HH:mm)
     */
    public static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATETIME_FORMATTER_STR);

    /**
     * 时间日期转换器(yyyy-MM-dd HH:mm:ss)
     */
    public static final DateTimeFormatter FULL_DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern(DATETIME_FULL_FORMATTER_STR);

    /**
     * 时间日期转换器(yyyyMMdd)
     */
    public static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern(DATE_FORMATTER_NO_SEPARATOR_STR);

    private static final String[] WEEKS_ARR = {"周一", "周二", "周三", "周四", "周五", "周六", "周日"};

    /**
     * 将时间格式字符串转换为时间
     *
     * @param strDate    字符串类型的时间
     * @param dateFormat 转换格式
     * @return 时间
     */
    public static Date strToDate(@NonNull String strDate, @NonNull String dateFormat) {
        return DATE_FORMAT_MAP.computeIfAbsent(dateFormat, FastDateFormat::getInstance).parse(strDate, new ParsePosition(0));
    }

    /**
     * 获取时间字符串
     *
     * @param dateDate   时间
     * @param dateFormat 时间格式有： yyyy-MM-dd HH:mm:ss 和 yyyyMMdd HHmmss 和 yyyyMMddhhmmss
     *                   注意字母y不能大写。
     * @return 时间字符串
     */
    public static String dateToStr(Date dateDate, @NonNull String dateFormat) {
        if(dateDate == null) return "";
        return DATE_FORMAT_MAP.computeIfAbsent(dateFormat, FastDateFormat::getInstance).format(dateDate);
    }

    /**
     * 两个时间段的相差的秒数
     *
     * @param beginDate 开始时间
     * @param endDate   结束时间
     * @return 秒数
     */
    public static long getSecond(Date beginDate, Date endDate) {
        return (endDate.getTime() - beginDate.getTime()) / 1000;
    }

    /**
     * 两个时间段的相差的分钟数
     *
     * @param beginDate 开始时间
     * @param endDate   结束时间
     * @return 分钟数
     */
    public static long getMinute(Date beginDate, Date endDate) {
        return getSecond(beginDate, endDate) / 60; // 转化minute
    }

    /**
     * 时间前推或后推分钟,其中second表示秒
     *
     * @param date    日期
     * @param seconds 秒，可以为负数(代表往前移)
     * @return 计算后的时间
     */
    public static Date calcSecond(final Date date, int seconds) {
        return new Date(((date.getTime() / 1000) + seconds) * 1000);
    }

    /**
     * 时间前推或后推分钟
     *
     * @param date    时间
     * @param minutes 分钟，可以为负数(代表往前移)
     * @return 计算后的时间
     */
    public static Date calcMinutes(final Date date, int minutes) {
        return calcSecond(date, minutes * 60);
    }

    /**
     * 获取延迟多少小时以后的时间
     *
     * @param date  时间
     * @param hours 小时，可以为负数(代表往前移)
     * @return 计算后的时间
     */
    public static Date calcHours(final Date date, int hours) {
        return calcMinutes(date, hours * 60);
    }

    /**
     * 得到一个时间延后或前移几天的时间
     *
     * @param date 时间
     * @param days 天数，可以为负数(代表往前移)
     * @return 计算后的时间
     */
    public static Date calcDays(final Date date, int days) {
        return calcHours(date, days * 24);
    }

    /**
     * 根据一个日期，返回是星期几的数字
     *
     * @param date 时间
     * @return 返回星期几的常量数字，例如{@link Calendar#SUNDAY}
     */
    public static int getWeek(Date date) {
        Calendar c = Calendar.getInstance();
        c.setTime(date);
        return c.get(Calendar.DAY_OF_WEEK);
    }

    /**
     * 根据一个日期，返回是星期几的字符串
     *
     * @param date 时间
     * @return 返回是星期几的字符串
     */
    public static String getWeekStr(Date date) {
        return getWeekStr(date.toInstant().atZone(DEFAULT_ZONE_ID).toLocalDateTime());
    }

    /**
     * 根据一个日期，返回是星期几的字符串
     *
     * @param date 时间
     * @return 返回是星期几的字符串
     */
    public static String getWeekStr(LocalDateTime date) {
        return WEEKS_ARR[date.getDayOfWeek().getValue() - 1];
    }

    /**
     * 根据一个日期，返回是星期几的字符串
     *
     * @param date 时间
     * @return 返回是星期几的字符串
     */
    public static String getWeekStr(LocalDate date) {
        return WEEKS_ARR[date.getDayOfWeek().getValue() - 1];
    }

    /**
     * 获取时间跟现在的时间比较后的字符串
     * <p>
     * 如果在同一天，返回时间
     * 如果在昨天，返回昨天
     * 如果在同一周，返回周几
     * 否则返回日期
     *
     * @param before 需要比较的时间
     * @return 比较后的字符串
     */
    public static String getDateStr(Date before) {
        LocalDateTime now = LocalDateTime.now();
        LocalDateTime bf = before.toInstant().atZone(DEFAULT_ZONE_ID).toLocalDateTime();
        LocalDate b = bf.toLocalDate();
        if (now.isBefore(bf)) {
            return bf.format(DATE_TIME_FORMATTER);
        }
        LocalDate a = now.toLocalDate();
        if (a.equals(b)) {
            return bf.format(TIME_FORMATTER);
        } else if (a.equals(b.plusDays(1))) {
            return "昨天";
        } else if (ChronoUnit.WEEKS.between(a, b) == 0) {
            return getWeekStr(bf);
        } else {
            return b.toString();
        }
    }

    /**
     * 输入分钟输出xx小时xx分钟
     *
     * @param minutes 分钟数
     * @return xx小时xx分钟
     */
    public static String getTimeStrByMinute(long minutes) {
        StringBuilder result = new StringBuilder();
        long hours = 0;
        if (minutes < 0) {
            minutes = 0;
        }
        if (minutes != 0) {
            hours = minutes / 60;
            minutes = minutes % 60;
        }
        if (hours > 0) {
            result.append(hours).append("小时");
        }
        if ((minutes != 0 && hours > 0) || hours == 0) {
            result.append(minutes).append("分钟");
        }
        return result.toString();
    }

    /**
     * 获取时间距今xx小时xx分钟
     *
     * @param time 时间
     * @return xx小时xx分钟
     */
    public static String getTimeStrByDate(@NonNull final Date time) {
        long minute = (time.getTime() - System.currentTimeMillis()) / (1000 * 60);
        return getTimeStrByMinute(minute);
    }

    /**
     * 输入分钟输出xx分隔符xx
     *
     * @param minutes   分钟
     * @param separator 分隔符
     * @return xx分隔符xx
     */
    public static String getHourStrByMinute(long minutes, String separator) {
        StringBuilder result = new StringBuilder();
        long hour = 0;
        if (minutes < 0) {
            minutes = 0;
        }
        if (minutes != 0) {
            hour = minutes / 60;
            minutes = minutes % 60;
        }
        if (hour < 10) {
            result.append('0');
        }
        result.append(hour).append(separator);
        if (minutes < 10) {
            result.append('0');
        }
        result.append(minutes);
        return result.toString();
    }

    /**
     * 输入xx:xx输出分钟
     *
     * @param hourStr   xx分隔符xx格式的时间
     * @param separator 分隔符
     * @return 分钟数
     */
    public static long getMinuteByHourStr(String hourStr, String separator) {
        String str[] = hourStr.split(separator);
        int hour = Integer.parseInt(str[0]);
        int minute = Integer.parseInt(str[1]);
        minute += hour * 60;
        return minute;
    }

    /**
     * 判断是否是同一月
     *
     * @param date1 日期1
     * @param date2 日期2
     * @return 是否是同一月
     */
    public static boolean isSameMonth(Date date1, Date date2) {
        if (date1 == null || date2 == null) {
            return false;
        }
        Calendar oneCalendar = Calendar.getInstance();
        oneCalendar.setTime(date1);

        Calendar twoCalendar = Calendar.getInstance();
        twoCalendar.setTime(date2);

        int oneYear = oneCalendar.get(Calendar.YEAR);
        int twoYear = twoCalendar.get(Calendar.YEAR);
        if (oneYear == twoYear) {
            int oneMonth = oneCalendar.get(Calendar.MONTH);
            int twoMonth = twoCalendar.get(Calendar.MONTH);
            return (oneMonth == twoMonth);
        }
        return false;
    }

    /**
     * 获取中午时间
     *
     * @param date 原时间
     * @return 中午时间
     */
    public static Date getNoon(final Date date) {
        Calendar now = Calendar.getInstance();
        now.setTime(date);
        now.set(Calendar.HOUR_OF_DAY, 12);
        now.set(Calendar.MINUTE, 0);
        now.set(Calendar.SECOND, 0);
        return now.getTime();
    }

    /**
     * 获取这个月的开始时间
     *
     * @param year  年份
     * @param month 月份1-12
     * @return 开始时间
     */
    public static Date getBeginTime(int year, int month) {
        return Date.from(YearMonth.of(year, month).atDay(1).atStartOfDay().atZone(DEFAULT_ZONE_ID).toInstant());
    }

    /**
     * 获取这个月的结束时间
     *
     * @param year  年份
     * @param month 月份1-12
     * @return 结束时间
     */
    public static Date getEndTime(int year, int month) {
        return Date.from(YearMonth.of(year, month).atEndOfMonth().atTime(23, 59, 59, 999).atZone(DEFAULT_ZONE_ID).toInstant());
    }

    /**
     * 得到上个月的最后一天
     * 不是最后一秒
     *
     * @return 最后一天，不是最后一秒
     */
    public static Date getPreMonthLastDay() {
        return Date.from(LocalDateTime.now().withDayOfMonth(1).minusDays(1).atZone(DEFAULT_ZONE_ID).toInstant());
    }

    /**
     * 获取两个时间段的所有时间
     * @param beginDate 开始时间
     * @param endDate   结束时间
     * @return 两个时间段的所有时间
     */
    public static List<LocalDate> getDatesBetweenTwoDate(LocalDate beginDate, LocalDate endDate) {
        // fixbugs 未初始化大小
        int maxLen = Math.abs((int)beginDate.until(endDate, ChronoUnit.DAYS));
        List<LocalDate> lDate = new ArrayList<>(maxLen + 2);
        lDate.add(beginDate);// 把开始时间加入集合
        if (endDate.isEqual(beginDate)) {
            return lDate;
        }
        LocalDate roll = beginDate;
        // 使用给定的 Date 设置此 Calendar 的时间
        while (true) {
            // 根据日历的规则，为给定的日历字段添加或减去指定的时间量
            roll = roll.plusDays(1);
            // 测试此日期是否在指定日期之后
            if (roll.isEqual(endDate)) {
                break;
            }
            lDate.add(roll);
        }
        lDate.add(endDate);// 把结束时间加入集合
        return lDate;
    }

    /**
     * 根据生日日期获取年龄
     *
     * @param birthDate 生日
     * @return 年龄
     */
    public static int getAge(@NonNull Date birthDate) {
        LocalDate now = LocalDate.now();
        LocalDate birth = birthDate.toInstant().atZone(DEFAULT_ZONE_ID).toLocalDate();
        int age = now.getYear() - birth.getYear();
        // 如果未到出生月份，则age - 1
        if (now.getMonthValue() < birth.getMonthValue()) {
            age -= 1;
        }
        if (age < 0) {
            age = 0;
        }
        return age;
    }


    /**
     * 获取上个月
     *
     * @return uuuu-MM格式的月份
     */
    public static String getLastMonth() {
        return YearMonth.now().minusMonths(1).toString();
    }

    /**
     * 获取当前月
     *
     * @return uuuu-MM格式的月份
     */
    public static String getCurrentMonth() {
        return YearMonth.now().toString();
    }

    /**
     * 计算两个日期之间相差的天数
     *
     * @param smdate 较小的时间
     * @param bdate  较大的时间
     * @return 相差天数
     */
    public static int daysBetween(Date smdate, Date bdate) {
        return (int) smdate.toInstant().atZone(DEFAULT_ZONE_ID).toLocalDate().until(bdate.toInstant().atZone(DEFAULT_ZONE_ID).toLocalDate(), ChronoUnit.DAYS);
    }

    /**
     * 获取当月天数
     *
     * @param monthStr uuuu-MM格式的月份
     * @return 当月有多少天
     */
    public static int getDaysOfMonth(String monthStr) {
        return YearMonth.parse(monthStr).atEndOfMonth().getDayOfMonth();
    }

    /**
     * 两日期之间的天数
     *
     * @param smdate uuuu-MM-dd格式，小的日期
     * @param bdate  uuuu-MM-dd格式， 大的日期
     * @return 天数
     */
    public static int daysBetween(String smdate, String bdate) {
        return (int) LocalDate.parse(smdate).until(LocalDate.parse(bdate), ChronoUnit.DAYS);
    }

    /**
     * 指定日期格式转换（yyyyMMdd），转为uuuu-MM-dd
     *
     * @param strDate yyyyMMdd格式的字符串
     * @return uuuu-MM-dd格式的字符串
     */
    public static String strYmdToDate(String strDate) {
        return LocalDate.parse(strDate, DATE_FORMATTER).toString();
    }

    /***
     * 根据月份字符串获取此月下个月的月份字符串
     * @param month 月份(uuuu-MM)
     * @return 下个月的月份(uuuu - MM)
     */
    public static String getNextMonth(String month) {
        return YearMonth.parse(month).plusMonths(1).toString();
    }

    /***
     * 根据月份字符串获取此月上个月的月份字符串
     * @param month 月份(uuuu-MM)
     * @return 上个月的月份(uuuu - MM)
     */
    public static String getLastMonth(String month) {
        return YearMonth.parse(month).minusMonths(1).toString();
    }

    /**
     * Excel 取出的日期数值类型，转换JAVA日期类型
     *
     * @param dateNum Excel里的日期数字
     * @return 日期
     */
    public static Date getExcelDate(Integer dateNum) {
        Calendar c = new GregorianCalendar(1900, 0, -1);
        Date d = c.getTime();
        //dateNum是距离1900年1月1日的天数
        return calcDays(d, dateNum);
    }

    /**
     * Excel 取出的日期数值类型，转换成yyyy-MM-dd格式
     * @param dateNum Excel里的日期数字
     * @return yyyy-MM-dd格式的日期
     */
    public static String getExcelDateToStr(Integer dateNum) {
        Calendar c = new GregorianCalendar(1900, 0, -1);
        Date d = c.getTime();
        //dateNum是距离1900年1月1日的天数
        return dateToStr(calcDays(d, dateNum), DATE_FORMATTER_STR);
    }

    /**
     * Excel 取出的日期数值类型，转换成yyyy-MM格式
     * @param dateNum Excel里的日期数字
     * @return yyyy-MM格式的日期
     */
    public static String getExcelMonthToStr(Integer dateNum) {
        Calendar c = new GregorianCalendar(1900, 0, -1);
        Date d = c.getTime();
        //dateNum是距离1900年1月1日的天数
        return dateToStr(calcDays(d, dateNum), "yyyy-MM");
    }

    /**
     * 获取一个月的日期
     * 把整个月的1号到31号遍历出来，按照自定义的格式
     *
     * @param month 月份
     * @param pattern 转换格式
     * @return 整个月的1号到31号
     */
    public static String[] days(final YearMonth month, final String pattern) {
        LocalDate m = month.atDay(1);
        int begin = 1, end = m.lengthOfMonth();
        String[] days = new String[end];
        DateTimeFormatter dtf = DateTimeFormatter.ofPattern(pattern);
        while (begin <= end) {
            m = m.withDayOfMonth(begin);
            days[begin - 1] = m.format(dtf);
            begin++;
        }
        return days;
    }

    /**
     * 根据日期判断来显示时间
     * 如果是同一天，返回时间
     * 如果是同一年，返回MM-dd HH:mm格式的日期时间
     * 否则返回完整时间日期
     *
     * @param base 当前日期
     * @param date 时间
     * @return 比较后的日期时间
     */
    public static String getSmartStr(LocalDate base, Date date) {
        LocalDateTime dt = date.toInstant().atZone(DEFAULT_ZONE_ID).toLocalDateTime();
        if (base.isEqual(dt.toLocalDate())) {
            return dt.format(TIME_FORMATTER);
        } else if (base.getYear() == dt.getYear()) {
            return dt.format(DateTimeFormatter.ofPattern("MM-dd HH:mm"));
        } else {
            return dt.format(DATE_TIME_FORMATTER);
        }
    }

    /**
     * 获取补考勤的开始工作日
     * @param date 当前日期
     * @param end 结束的天（几号）
     * @return 开始时间
     */
    public static String getBeginDay(Date date, int end) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int begin = calendar.get(Calendar.DAY_OF_MONTH);
        // 如果现在的日期大于截至日期，就从截至日期后一天开始
        // 如果现在的日期小于等于截至日期，就从截至日期的后一天的前一个月开始
        if (begin > end) {
            calendar.set(Calendar.DAY_OF_MONTH, end + 1);
            return DateUtil.dateToStr(calendar.getTime(), DATE_FORMATTER_STR);
        } else {
            calendar.set(Calendar.DAY_OF_MONTH, end + 1);
            calendar.add(Calendar.MONTH, -1);
            return DateUtil.dateToStr(calendar.getTime(), DATE_FORMATTER_STR);
        }
    }

    /**
     *  获取某段时间中间经过的日期yyyy-MM-dd格式
     * @param date 开始日期
     * @param end 结束的天（几号）
     * @param separator 年月日分隔符
     * @param notNow 是否不包含现在
     * @return 某段时间中间经过的日期yyyy-MM-dd格式
     */
    public static List<String> getDay(Date date, int end, String separator, boolean notNow) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        int begin = calendar.get(Calendar.DAY_OF_MONTH);
        int year = calendar.get(Calendar.YEAR);
        int month = calendar.get(Calendar.MONTH) + 1;
        List<String> result = new ArrayList<>();
        String monthStr = month + "";
        if (month < 10) {
            monthStr = "0" + monthStr;
        }
        if (begin > end) {
            for (int i = end + 1; i <= begin; i++) {
                String dayStr = i + "";
                if (i < 10) {
                    dayStr = "0" + i;
                }
                result.add(year + separator + monthStr + separator + dayStr);
            }
        } else {
            for (int i = 1; i <= begin; i++) {
                String dayStr = i + "";
                if (i < 10) {
                    dayStr = "0" + i;
                }
                result.add(year + separator + monthStr + separator + dayStr);
            }
            calendar.add(Calendar.MONTH, -1);
            int pYear = calendar.get(Calendar.YEAR);
            int pMonth = calendar.get(Calendar.MONTH) + 1;
            monthStr = pMonth + "";
            if (pMonth < 10) {
                monthStr = "0" + monthStr;
            }
            int monthEnd = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
            for (int i = end + 1; i <= monthEnd; i++) {
                String dayStr = i + "";
                if (i < 10) {
                    dayStr = "0" + i;
                }
                result.add(pYear + separator + monthStr + separator + dayStr);
            }
        }
        if (notNow && !result.isEmpty()) {
            String nowStr = new SimpleDateFormat("yyyy" + separator + "MM" + separator + "dd").format(date);
            result.remove(nowStr);
        }
        return result;
    }

    /**
     * 时间转日期
     * @param date 时间
     * @return 日期
     */
    public static LocalDate toLocalDate(Date date){
        return date.toInstant().atZone(DEFAULT_ZONE_ID).toLocalDate();
    }

}